package com.anttribe.dbdocument;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.sql.DataSource;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import com.anttribe.components.db.metadata.entity.Database;
import com.anttribe.components.db.metadata.entity.DbSchema;
import com.anttribe.components.db.metadata.entity.DbTable;
import com.anttribe.components.db.metadata.supplier.DatabaseSupplier;
import com.anttribe.components.db.metadata.supplier.DatabaseSupplierFactory;
import com.anttribe.components.db.sql.SqlOperation;
import com.anttribe.components.db.sql.SqlStatementFactory;
import com.anttribe.components.db.sql.format.SqlFormatter;
import com.anttribe.components.db.sql.statement.CreateTableSqlStatement;
import com.anttribe.components.doc.generator.DocGenerator;
import com.anttribe.components.doc.generator.GeneralDocGenerator;
import com.anttribe.components.doc.generator.in.DataModel;
import com.anttribe.components.doc.generator.out.GeneratorOutput;
import com.anttribe.dbdocument.config.DBDataProcessorConfiguration;
import com.anttribe.dbdocument.config.DbDocumentConfiguration;
import com.anttribe.dbdocument.constants.Constants;
import com.anttribe.dbdocument.exception.DbDocumentGeneratorException;
import com.anttribe.dbdocument.mapper.DbTableMapper;
import com.anttribe.dbdocument.model.DbTableExt;
import com.google.common.collect.Lists;

import lombok.extern.slf4j.Slf4j;

/**
 * @author zhaoyong
 * @date 2021/10/5 0005
 */
@Slf4j
public class DbDocumentGenerator {

    /**
     * 配置项
     */
    private DbDocumentConfiguration configuration;

    /**
     * 文档生成器
     */
    private DocGenerator docGenerator;

    public DbDocumentGenerator(DbDocumentConfiguration configuration) {
        this.configuration = configuration;
        this.docGenerator = new GeneralDocGenerator(this.configuration.getDocGeneratorConfiguration());
    }

    /**
     * 给定数据源信息和数据库信息，生成数据库文档
     *
     * @param dataSource
     *            DataSource
     * @return Output
     */
    public GeneratorOutput generate(DataSource dataSource) {
        return this.generate(dataSource, new HashMap<>());
    }

    /**
     * 给定数据源信息和数据库信息，生成数据库文档
     *
     * @param dataSource
     *            DataSource
     * @param extraDatas
     *            扩展参数
     * @return GeneratorOutput
     */
    public GeneratorOutput generate(DataSource dataSource, Map<String, Object> extraDatas) {
        // 参数校验
        Optional.ofNullable(dataSource)
            .orElseThrow(() -> new DbDocumentGeneratorException("DataSource must not be null"));

        // 构造DataModel
        DataModel dataModel =
            new DBDataProcessor(dataSource, this.configuration.getDbDataProcessorConfiguration()).process();
        if (null == dataModel || MapUtils.isEmpty(dataModel.getModelMap())) {
            log.error("Can not process db data with datasource: {}", dataSource);
            throw new DbDocumentGeneratorException("Can not process db data");
        }

        // 添加额外数据
        if (!MapUtils.isEmpty(extraDatas)) {
            dataModel.addAllObjects(extraDatas);
        }

        // 生成文档
        return docGenerator.generate(dataModel);
    }

    /**
     * 数据库数据处理
     */
    class DBDataProcessor {

        /**
         * 数据源对象
         */
        private DataSource dataSource;

        /**
         * 数据处理配置
         */
        private DBDataProcessorConfiguration dbDataProcessorConfiguration;

        public DBDataProcessor(DataSource dataSource, DBDataProcessorConfiguration dbDataProcessorConfiguration) {
            Optional.ofNullable(dbDataProcessorConfiguration)
                .orElseThrow(() -> new DbDocumentGeneratorException("DbDataProcessorConfiguration must not be null"));

            this.dataSource = dataSource;
            this.dbDataProcessorConfiguration = dbDataProcessorConfiguration;
        }

        /**
         * 处理数据，获取数据模型
         *
         * @return DataModel
         */
        public DataModel process() {
            DatabaseSupplier supplier = DatabaseSupplierFactory.me().getDatabaseSupplier(this.dataSource);
            if (null == supplier) {
                log.error("Can not get DatabaseSupplier with datasource: {}", this.dataSource);
                throw new DbDocumentGeneratorException("Can not get DatabaseSupplier");
            }

            DbSchema dbSchema = supplier.getDbSchema();
            if (null == dbSchema) {
                log.error("Failed to get db schema info");
                throw new DbDocumentGeneratorException("Failed to get db schema info");
            }

            // 获取数据库相关信息
            Database database = supplier.getDatabase();
            // 数据库表
            List<DbTable> dbTables = supplier.getTables();
            // 过滤表
            filterDbTableWithConfiguration(dbTables);

            List<DbTableExt> dbTableExts = Lists.newArrayList();
            if (CollectionUtils.isNotEmpty(dbTables)) {
                CreateTableSqlStatement sqlStatement = (CreateTableSqlStatement)SqlStatementFactory.me()
                    .createSqlStatement(SqlOperation.CREATE_TABLE, supplier.getDialect());
                dbTableExts = dbTables.stream().map(dbTable -> {
                    DbTableExt dbTableExt = DbTableMapper.INSTANCE.toDbTableExt(dbTable);
                    // 表结构语句
                    sqlStatement.setDbTable(dbTable);
                    String createTableSql = sqlStatement
                        .generateSql(SqlFormatter.FormatOptions.builder().prettyFormat(Boolean.TRUE).build());
                    dbTableExt.setCreateTableSql(createTableSql);
                    return dbTableExt;
                }).collect(Collectors.toList());
            }

            // 构造DataModel
            DataModel dataModel = new DataModel();
            dataModel.addObject(Constants.DbDataKey.KEY_DATABASE, database);
            dataModel.addObject(Constants.DbDataKey.KEY_DBSCHEMA, dbSchema);
            dataModel.addObject(Constants.DbDataKey.KEY_DBTABLES, dbTableExts);
            return dataModel;
        }

        /**
         * 按照配置过滤表
         * 
         * @param dbTables
         *            List<DbTable>
         */
        private void filterDbTableWithConfiguration(List<DbTable> dbTables) {
            if (CollectionUtils.isEmpty(dbTables)) {
                return;
            }

            filterDesignatedDbTables(dbTables);
            filterExcludeDbTables(dbTables);
        }

        /**
         * 过滤指定的表（表名/表前缀/表后缀）
         * 
         * @param dbTables
         */
        private void filterDesignatedDbTables(List<DbTable> dbTables) {
            if (CollectionUtils.isNotEmpty(dbTables)
                && CollectionUtils.isNotEmpty(this.dbDataProcessorConfiguration.getDesignatedTableNames())) {
                dbTables.removeIf(dbTable -> this.dbDataProcessorConfiguration.getDesignatedTableNames().stream()
                    .noneMatch(designatedTableName -> StringUtils.equalsIgnoreCase(dbTable.getTableName(),
                        designatedTableName)));
            } else if (CollectionUtils.isNotEmpty(dbTables)
                && CollectionUtils.isNotEmpty(this.dbDataProcessorConfiguration.getDesignatedTablePrefixs())) {
                dbTables.removeIf(dbTable -> this.dbDataProcessorConfiguration.getDesignatedTablePrefixs().stream()
                    .noneMatch(designatedTablePrefix -> StringUtils.startsWithIgnoreCase(dbTable.getTableName(),
                        designatedTablePrefix)));
            } else if (CollectionUtils.isNotEmpty(dbTables)
                && CollectionUtils.isNotEmpty(this.dbDataProcessorConfiguration.getDesignatedTableSuffixs())) {
                dbTables.removeIf(dbTable -> this.dbDataProcessorConfiguration.getDesignatedTableSuffixs().stream()
                    .noneMatch(designatedTableSuffix -> StringUtils.endsWithIgnoreCase(dbTable.getTableName(),
                        designatedTableSuffix)));
            }
        }

        /**
         * 过滤排除的表（表名/表前缀/表后缀）
         *
         * @param dbTables
         */
        private void filterExcludeDbTables(List<DbTable> dbTables) {
            if (CollectionUtils.isNotEmpty(dbTables)
                && CollectionUtils.isNotEmpty(this.dbDataProcessorConfiguration.getExcludeTableNames())) {
                dbTables.removeIf(dbTable -> this.dbDataProcessorConfiguration.getExcludeTableNames().stream().anyMatch(
                    excludeTableName -> StringUtils.equalsIgnoreCase(dbTable.getTableName(), excludeTableName)));
            } else if (CollectionUtils.isNotEmpty(dbTables)
                && CollectionUtils.isNotEmpty(this.dbDataProcessorConfiguration.getExcludeTablePrefixs())) {
                dbTables.removeIf(dbTable -> this.dbDataProcessorConfiguration.getExcludeTablePrefixs().stream()
                    .anyMatch(excludeTablePrefix -> StringUtils.startsWithIgnoreCase(dbTable.getTableName(),
                        excludeTablePrefix)));
            } else if (CollectionUtils.isNotEmpty(dbTables)
                && CollectionUtils.isNotEmpty(this.dbDataProcessorConfiguration.getExcludeTableSuffixs())) {
                dbTables.removeIf(dbTable -> this.dbDataProcessorConfiguration.getExcludeTableSuffixs().stream()
                    .anyMatch(excludeTableSuffix -> StringUtils.endsWithIgnoreCase(dbTable.getTableName(),
                        excludeTableSuffix)));
            }
        }

    }

}
